Skip to content

fix(mobile): give a lost agent connection one transcript state - #9416

Open
iscekic wants to merge 16 commits into
mainfrom
drain/session-connection-lost-one-state
Open

iscekic wants to merge 16 commits into
mainfrom
drain/session-connection-lost-one-state

Conversation

@iscekic

@iscekic iscekic commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

Evidence

Evidence: demonstrated — on emulator-5554 at the default text size and density against the local dev stack, with the transcript read held open so the loading window stays measurable: a writable open's loading slot painted the read-only footer's reserve at the previous head (a 17.5 dp note bar at y 2112–2152 px and a 48 dp action bar at y 2184–2308 px in the footer's own gray, inside the band that starts at y 2080 px) and paints the composer's own row at this head (the field's box and the two trailing controls in the placeholder fill on the page background, with no footer gray anywhere in the frame — the controls standing 124 px, 48 pt at the device's 2.625 density); a read-only session keeps the footer's area and its footer lands on the same frame ([0, 2080][1080, 2337] px); the reported frame itself is covered by the mounted suite, where a stalled open with a connection-lost indicator now renders one retryable card with its Retry instead of the skeleton and a Connection lost line, for indicator types error, info, progress and warning.

Problem

The session screen had no single owner for a lost agent transport:

  • resolveSessionTerminalError only treated a status indicator of type error as the body's failure, and the SDK's own lost-transport code (agent-connection-lost) carried catalog copy instead of a class, so the classifier called it unknown: copy written for an unknown load failure and no Retry.
  • An indicator carrying that code with any other type resolved no body failure at all, so the skeleton kept painting while the footer row added Connection lost in the same frame — a loading state and a failure on one screen.
  • Once the body did state the failure, the fixed footer row and the composer reason restated it (The session could not be loaded. Retry first. beside Send), so one failure was stated twice with one meaning.

Five defects of the same window survived that change, and the design review named them across its successive reviews:

  • While the open still had nothing to show, the screen painted an editable Message composer beside the loading skeleton, even though the session's own transport had not answered and the composer was not usable. A read-only session then replaced that composer with the read-only footer, so one screen showed writing controls the session did not have and then jumped to the state it did.
  • The reserve that replaced that composer was the read-only footer's shape for every session, because it did not consult isReadOnly. A writable open (the common case, and the one whose composer is what lands) held the footer's 97.9 dp block and then swapped to its ~56 dp composer, so the slot jumped by the difference and briefly described the wrong footer.
  • The header context pill collapsed to a bare ring in that window and grew its value only when usage arrived, so it read as broken beside the pill the same screen shows once loaded.
  • The reserved footer painted as an empty tinted block: its bars kept the shared Skeleton's default fill, and the theme's muted equals the secondary fill of the footer the area stands in for, so the two bars painted the container's own colour.
  • The header pill painted a second dash in its cost slot while the session opened. The dash read as missing data rather than as loading, and it was narrower than the cost that replaces it, so the pill grew a value out of a smaller pill.
  • The same window still painted one real dash beside a loading bar in that row, which reads as half-painted content rather than as one placeholder. The value row is one bar while it loads and one bar per value once it settles.

Change

  • apps/mobile/src/components/agents/session-terminal-error.ts
    • The SDK's lost-transport code resolves to a retryable terminal failure whatever type its indicator carries: title Couldn't load this session, message Connection trouble. Please retry in a moment., Retry. No new copy; both strings already exist in every mobile locale.
    • isRetryableTerminalFailure answers true for that code, so the other surfaces agree.
    • The resolver takes a connectionLost fact for the app-wide transport leg, which counts only while the open still has nothing to show.
  • apps/mobile/src/components/agents/session-detail-content.tsx
    • The terminal error is resolved once, before the footer rows, and handed to the body (precedence unchanged: terminal error, then slow-load, then skeleton).
    • While the body states a failure, the fixed footer status row and the composer reason stay silent: one failure, one statement, one Retry. The composer stays mounted and editable.
    • The bottom slot belongs to the session, and it reserves the shape this session's own kind lands in: while the open has nothing to show and no failure, status or pending ask, a read-only session holds the read-only footer's area and every other session holds the composer's, picked from isReadOnly (the kind the session resolved to), so neither case jumps when the open resolves. Both reserves are placeholders, so neither offers a control. A failure, an agent status or a pending ask is the session speaking and keeps the composer it already has.
    • A loaded transcript keeps its rows, its footer status line and its sheet Retry; an empty session that already settled keeps its empty state.
  • apps/mobile/src/components/agents/session-detail-skeleton.tsx
    • The placeholder bubbles can shrink into a body shorter than they are instead of overflowing it, so a placeholder can never be sliced by the row below it.
    • SessionReadOnlyFooterPlaceholder holds the read-only footer's area — its container, a line and a button bar at the footer's own child sizes — so the footer lands on its own geometry instead of replacing a composer.
    • Its bars take bg-muted-soft, the one gray that differs from the bg-secondary fill the area reserves, so the reserved area reads as loading instead of as an empty block.
    • Its sizes are written in points, not scale steps: this app's rem is 14 pt, so h-11/h-12 painted 38.5 pt and 42 pt against the field's 44 pt and CONTROL_HIT_TARGET's 48 pt on Android (44 on iOS) and the field follows the composer's own height rule (its input line box times the live system font scale, plus the input's vertical padding and the field's 1 pt border on each side) so a larger text size neither clips it nor leaves a gap, and SessionComposerSkeleton mirrors the composer's own input row — the field's 44 dp box and the two trailing controls at their tap-target size in the row's gap — instead of one bar, so the composer lands on the row the placeholder held. The route paints the same component before the session's metadata arrives.
    • Merged with main: the placeholder takes main's removal of its exit fade (the transcript takes its place in the same commit, so no snapshot paints over the real rows) and keeps this branch's bubble geometry — the bubbles stay the list's flex items, so a body shorter than they are compresses them and clips at its own edge instead of painting over the row below it.
  • apps/mobile/src/components/agents/context-usage-display.ts
    • A session that has not reported usage yet keeps both value slots of the header pill, instead of collapsing to a bare ring; only the accessibility label names usage unavailable once the session has settled.
    • The cost slot states what it paints: the cost label, the dashes the values will replace, or nothing. The width no longer depends on which value the session ends up reporting, so no state grows or shrinks the pill.
    • While the open still loads, the row's two dashes keep their layout and the cost slot is marked loading, which the screen paints as one bar over that row; after the change a dash beside a bar no longer reads as half-painted content.
  • apps/mobile/src/components/agents/session-context-metrics.tsx
    • The loading row paints invisible and one bg-muted-soft bar fills a full-bleed box over it, so the placeholder takes the settled row's own text metrics at any font scale instead of a width of its own.
    • That box centres the bar in the row rather than naming a vertical inset, because the row's height follows the font too. The value row renders when either it or the cost slot has content.
  • Tests: session-terminal-error.test.ts covers the lost code on every indicator type and the app-wide leg, plus the loaded-transcript exemption; session-detail-content.test.ts covers the reported frame (skeleton replaced by one retryable card, no footer line, no send reason), the rewritten footer expectations, and the bottom slot (held area with no composer, then the read-only footer on that same frame); context-usage-display.test.ts and session-context-metrics.test.ts cover the dash-marked slots and the single loading bar that replaces the dash while the row loads; session-detail-skeleton.test.ts pins the reserved footer's geometry and the fill that stands off it, and fails if a bar goes back to the container's own colour.

Verification

  • Merged main at b5b761e5b; the conflicts in context-usage-display.ts and session-context-metrics.tsx keep both sides — main's badge formatter and left-to-right cost run, and this branch's cost-slot model — and the suite that pins the direction now stubs the pill's skeleton, so it loads in the pure project without the animation package.
  • pnpm --filter kilo-app exec vitest run src/components/agents/ at this head: 296 files, 5317 tests pass.
  • pnpm --filter kilo-app run typecheck: clean after the tRPC and harness SDK builds. oxlint (apps/mobile/.oxlintrc.json) and oxfmt: clean on every changed file.
  • Device, emulator-5554 at the default text size and density, local dev stack, fixture session ses_00000000000LiveCliStream01:
    • merge base 2b4f203d3c: the open's loading frame, in all four design-gate cells, paints the Message composer with mic and send and a 51.0 dp context ring; the session then settles read-only with the This is a read-only session footer and Continue, and the composer is replaced by it.
    • reviewed head 14f4a7f594: the same loading frame paints the reserved footer area (no composer) and a 78.1 dp pill with both value slots, but the reserved footer's two bars and the pill's second dash are indistinguishable from their own surfaces.
    • this head 630f55d95: with the API held for 20 s so the loading window can be captured frame by frame, the reserved footer paints two bars — 46 px of note line and 126 px of button floor — and the pill holds a 73 px cost bar at the baseline of its dash. With the API refused, the same screen settles into the retryable failure with the dash in that same slot, and the pill keeps its width (97.1 dp / 97.5 dp / 97.9 dp; frame measurements in ## Evidence).
    • this head 2581362723: the loading frame paints no dash. In all four cells the value row is 40.0 dp and the bar fills it — [346.3, 93.0, 40.0, 7.2] inside the row at [346.3, 89.5, 40.0, 14.1], vertically centred, mirrored to [25.5, 93.0] in the Arabic cell. The pill is 97.9 dp while loading and 97.1 dp once the open settles with its cost (— 8.4 dp at [910, 235], $0.40 27.4 dp at [941, 235]), where the merge base's same frame is a 51.0 dp ring.
    • this head eae9f3804: with the app's data cleared so the open has nothing cached, the app-wide transport leg exhausted (the ingest socket unreachable) and the HTTP API held so the open stays unresolved, all four cells paint the placeholder and no composer. The skeleton body ends at y 792.4 dp, its last bubble [14.1, 732.6, 255.6, 48.8] 11.1 dp above that edge, and SessionReadOnlyFooterPlaceholder occupies [0, 792.4, 411.4, 97.9] with its 17.5 dp note bar and 48 dp button floor.
    • merge base 0a1a74b71, same staging and same cleared app: the same three bubbles at the same offsets, but the body ends at y 824.4 dp on the ChatComposer and no placeholder is painted — the composer inside the loading window this change removes.
    • this head b5b761e5b, one staging for both: the session's transcript socket stalled while its metadata arrived. The merge base e38da3eab paints the failure card with Retry, a second The session could not be loaded. Retry first. line under it, and the composer; this head paints the same card with Retry and no second line. In the loading window the base paints the Message composer where this head paints the reserved footer area — its note bar at [356, 2110, 724, 2156] and its button bar [37, 2184, 1043, 2310] (48 dp) inside the footer's own container [0, 2080, 1080, 2337] — and the header pill paints no value text, only the bar over the row.
    • this head 282ec6c01, writable open with the transcript read held: the slot paints the composer row (field box plus two controls) in the placeholder fill, the controls 124 px tall; the same staging at head b5b761e5b paints the read-only footer's two bars in bg-muted-soft instead, and both frames are attached in the comments that report these runs.
    • this head 282ec6c01, cloud-agent session with no readable transcript: the same slot holds the composer row while the read is in flight, then the retryable card with its Retry takes the body.
  • Mounted reproduction of the reported frame: before the change a stalled open with a connection-lost indicator renders the skeleton and the Connection lost line; after it, neither — the retryable card with its Retry instead. Covered for indicator types error, info, progress and warning, and for an exhausted app-wide transport leg.
  • Design gate: 4 cells, 0 errors, 4 warnings.
    • At this head 282ec6c01, on the agent chat session route for the dev fixture session: 4 cells, 0 errors, 4 warnings — one unsettled warning per cell, because the fixture session streams while the cells are measured (11–13 s against the gate's 8 s settle window), not a layout finding. Nothing this change paints is flagged: no clipped element, no layout shift, no touch target, no safe area and no RTL ordering finding.
    • The merge-base run of the same link, taken after switching the checkout to e38da3eab (this branch's merge base on main), restarting Metro and reloading the app: 4 cells, 0 errors, 8 warnings — the same four unsettled ones, plus the composer voice control's touch target in all four cells, which the head's captured frame does not paint. Nothing this change paints is flagged in either run.
    • The two runs were captured back to back with one staging: the same fixture session, seeded fresh for each run, and the same screen state (the fixture streaming) in every cell of both runs. The probe dumps confirm the code each run measured — the head cells mount the composer's placeholder row and never the composer, the base cells mount the composer.
    • Both runs and the contact sheet are uploaded for this head; the contact sheet is attached in the comment that reports these runs.

Limitations

The exact dark cell from the reporting run also needs the socket to close while the transcript fetch is still pending. In the local stack both ride port 8800, so a stopped worker fails the fetch first (the error-atom state) and a dropped port hangs both: that split cannot be staged on this VM. The indicator path is therefore proved in the mounted suite, and the device run covers the loading, the read-only and the load-error states.

The design gate's default observation window closes the cell before this fixture's transcript settles, so the runs above show either the first painted frame or the loading frame; the settled read-only frame was measured on the same device with a longer observation window (--observe-ms 7000), which also reports the transcript's own scroll-in as layout-shift errors on the merge base and on this branch alike. The loading frame in the runs above is the gate's own capture right after the link opens, while this fixture's transcript is still unresolved; an earlier head held that window open with a delayed API instead, and that capture also showed the app's own slow-load card once the delay outlasted its threshold.

A writable pending window was also staged on the device by holding one read (cliSessionsV2.getSessionMessagesPage) for 9 s. That makes the window reproducible and is not the app's own timing: past ~20 s the read's client deadline turns the open into the retryable card instead. While a session's kind is still unresolved (metadata in flight) the slot is the composer's, so a read-only session passes through the composer row before its footer; both are placeholders and neither offers a control.

Production prerequisites

None.

Proxy answers

The owner's proxy gave these answers in the owner's place.

  1. Question: Fix shape: Measured on this VM: a stalled open (0 messages) with a lost agent transport paints the loading skeleton and a 'Connection lost' line in the same frame, and with a real error-type indicator the body instead shows 'Couldn't load this session / Failed to load session details' with NO Retry (the SDK's own connection-lost code falls into the 'unknown' class). Which shape do you want?
    Answer: Both: retryable error for a lost transport AND a non-clipping skeleton for ordinary loading
    Confidence: 0.55 · Precedent: Fix the root cause and the class of the issue: "if anything repeats the same mistake over and over again, fix the class" (00eb2a1d3e4b)
  2. Question: Retry on lost: A lost agent connection currently lands in the terminal-error classifier as 'unknown' (copy aimed at an unknown load failure, no Retry). Should it become a retryable transient failure?
    Answer: Yes: transient class, existing 'Connection trouble' copy, Retry present
    Confidence: 0.80 · Precedent: Reuse as much as possible of the current implementation: "reuse as much as possible from the current impl" (0a2b68365a4b); plans cover the retryable-error state (df491946927a)
  3. Question: One statement: When the body already states the failure, the fixed footer status row and the composer reason repeat it (measured: the error card plus 'The session could not be loaded. Retry first.' beside send). Suppress them?
    Answer: Suppress both while the body states the failure
    Confidence: 0.70 · Precedent: Remove redundant copy and keep surfaces consistent: "go make the public dashboard a delight to use" (0e57ea66f95b); best-in-class mobile UX (00eb2a1d3e4b)
  4. Question: App-wide leg: The app-wide socket leg can be exhausted while the session transcript is still loading (no SDK status atom at all). Should that also replace a stalled skeleton with the retryable error?
    Answer: Yes, while the transcript is still loading
    Confidence: 0.60 · Precedent: Fix the class on every affected variant: "fix this for all shared services model pickers on all surfaces" (75f8833fe8c1)
  5. Question: Loading pill: The design critic's last finding is not pre-existing: the merge base paints the pill as a bare ring (51.0 dp, no dash, no bar), and this head paints ring + '—' + a 27.8 dp skeleton bar (97.9 dp). Fix it now, or leave it and let the owner decide?
    Answer: Fix: hide the dash while loading, one bar spanning the 40.2 dp value row
    Confidence: 0.85 · Precedent: UX rule 'Stop mobile design and UX defects before they land, with a design gate': session 6341f41c09ab, owner wrote 'I very often have design/ux fixes sessions - I don't want to have those'. Also, pushing after an approval is fine: session df491946927a, 'you can push after an approval without issue'.
  6. Question: Finding 1: Design review finding 1 (composer shape): in this head the pending open reserves the read-only footer's area for EVERY session — isReadOnlyFooterPlaceholderVisible = isSessionStatePending && !hasBlockingInteraction && !isReadOnlyTranscript, and isReadOnlyTranscript = isReadOnly && messages.length > 0. So a writable session (the common case; the composer is what lands) paints the footer's 97.9 dp block and then swaps to the ~56 dp composer, while a read-only session (the gate's CLI-stream fixture link) lands on that block at the identical frame. The critic read the base run's loading frame (a composer) and called the reserve a composer skeleton. How should the reserve pick its shape?
    Answer: Pick the reserved shape from isReadOnly
    Confidence: 0.80 · Precedent: UX and design quality: 'no layout shift ... fix the whole class of each defect' (session 00eb2a1d3e4b, "CLS is a massive enemy"); reuse existing components (session 0a2b68365a4b, "reuse as much as possible from the current impl")
  7. Question: Finding 2: Design review finding 2 (bar fill): it asks to 'use the same muted skeleton fill token as the message bubble skeletons'. That suggested token cannot work: the theme's --muted equals --secondary in both palettes (light #f0eee6/#f0eee6, dark #1f1f24/#1f1f24, pinned by guard tests in three other components), and the reserved area paints the footer's own bg-secondary, so bars in the bubbles' token paint the footer's fill exactly — the blank tinted block this branch already fixed once. bg-muted-soft is the house token for a bar on a secondary surface (profile credits, hub card, PR review checks, the header pill). Its bar is also lighter than the filled Continue button it reserves. How do you want it answered?
    Answer: Keep bg-muted-soft, state the reason in the body
    Confidence: 0.72 · Precedent: Keep similar elements consistent across surfaces and improve within the design system (session df491946927a, accepted "Improve within the design system"); simplest design (session a30dc8dc131a, "Simplest please")

The session screen could paint the loading skeleton and a Connection lost
line in the same frame, with the last skeleton bubble sliced by the row, and
the load-error card restated the same failure again beside Send.

A lost agent transport now owns the body: the SDK's own connection-lost code
resolves to a retryable terminal failure with the existing connection-trouble
copy and Retry, whatever type the status indicator carries, and an exhausted
app-wide transport leg does the same while the open still has nothing to
show. While the body states a failure, the fixed footer row and the composer
reason stay silent, so one failure is stated once with the Retry that
resolves it. The skeleton's bubbles can shrink instead of overflowing a body
shorter than they are.
@iscekic iscekic added drain Work item from the findings drain explorer:android Finding surfaced by the Android explorer labels Nov 1, 2026
@iscekic

iscekic commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Device proof on the Android emulator against the local dev stack, with the session-ingest worker stopped so the session open fails.

Before the change the load-error card and the composer reason state the same failure twice; after the change the card states it once with its Retry and no footer row.

Design gate contact sheet for the four standard cells (0 errors, 4 pre-existing touch-target warnings).

Load error before the change

Load error after the change

Design gate contact sheet

@kilo-code-bot

kilo-code-bot Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commit reads the composer's font scale from useLiveFontScale() and adds the field wrapper's 2 pt border, so the reserved composer slot now matches the row it stands in for; both earlier findings are resolved.

Assumptions: [INFERENCE] I did not run the mobile suite or measure a device frame in this read-only session. Heights come from chat-composer.tsx:139-141,440-446 (lineHeight * fontScale + TEXT_INPUT_VERTICAL_PADDING, height: measureHeight) and chat-composer-input-row.tsx (border border-border, CONTROL_HIT_TARGET 48 Android / 44 iOS). Note for future coverage: the updated suite mocks useLiveFontScale itself (session-detail-skeleton.test.ts:22-26), so it pins the height rule but would still pass if the component went back to useWindowDimensions; the hook's live-vs-fallback precedence is not exercised by this test.

Files Reviewed (2 files)
  • apps/mobile/src/components/agents/session-detail-skeleton.tsx - 0 issues (previously reported stale-scale and missing-border findings verified fixed)
  • apps/mobile/src/components/agents/session-detail-skeleton.test.ts - 0 issues
Previous Review Summaries (13 snapshots, latest commit 557fd81)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 557fd81)

Status: 2 Issues Found | Recommendation: Address before merge

Executive Summary

The composer slot now follows the font-scaled field height, but it reads the font scale from RN's cached dimensions instead of the app's live scale, and still omits the field wrapper's 2 pt border, so the slot can still disagree with the composer it reserves. The bubble/skeleton placeholder from the prior commit is unchanged and remains correct.

Assumptions: [INFERENCE] I did not run the mobile suite or measure a device frame in this read-only session. Heights come from chat-composer.tsx:139-141,444-446 (20 * fontScale + 24), chat-composer-input-row.tsx:194-196 (border border-border, CONTROL_HIT_TARGET 48 Android / 44 iOS), and session-detail-skeleton.tsx:82-84,105,113. The stale-scale claim is taken from the repo's own docstring in lib/font-scale/system-font-scale.tsx.

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/components/agents/session-detail-skeleton.tsx 105 The slot reads useWindowDimensions().fontScale, which this repo documents as stale while the app is foregrounded (font-size changes are handled in place, so Dimensions never refreshes); the composer it replaces reads useLiveFontScale(), so after an in-place text-size change the reserved field stays at the launch scale and the composer lands at a different height.
apps/mobile/src/components/agents/session-detail-skeleton.tsx 113 The reserved field is 20 * fontScale + 24 but the composer's field sits inside a border border-border wrapper, so its box is 2 pt taller. The slot is 2 pt short at font scale 1 on iOS (Android's 48 pt controls hide it) and at every larger scale on both platforms.
Files Reviewed (2 files)
  • apps/mobile/src/components/agents/session-detail-skeleton.tsx - 2 issues
  • apps/mobile/src/components/agents/session-detail-skeleton.test.ts - 0 issues (the added fieldBoxHeight assertions pin the new height rule but mock useWindowDimensions, so they cannot catch the stale-scale source)

Fix these issues in Kilo Cloud

Previous review (commit 09c5885)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The incremental commit replaces the composer placeholder's rem scale steps with platform point sizes, resolving the previous gap at the default scale, but the field still reserves a fixed 44 pt while the real composer field grows with the system font scale.

Assumptions: [INFERENCE] I did not run the mobile suite or measure a device frame in this read-only session. Sizes come from chat-composer.tsx (TEXT_INPUT_LINE_HEIGHT 20, TEXT_INPUT_VERTICAL_PADDING 24, fontScale from useLiveFontScale) and CONTROL_HIT_TARGET (48 Android / 44 iOS), plus the repo's convention that [Npx] arbitrary values are points (as session-context-metrics.tsx:91 documents).

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/components/agents/session-detail-skeleton.tsx 102 The field reserves a fixed 44 pt without the composer wrapper's border, and the real field (20 * fontScale + 24) grows with the system font scale, so the slot is ~2 pt short at the default scale on iOS and up to 20 * (fontScale - 1) + 2 pt short at larger text sizes
Files Reviewed (2 files)
  • apps/mobile/src/components/agents/session-detail-skeleton.tsx - 1 issue
  • apps/mobile/src/components/agents/session-detail-skeleton.test.ts - 0 issues (test pins the same fixed 44 pt field)

Resolved since the previous review: the prior h-11/h-12 6–7.5 pt gap at the default scale (the controls now use 48/44 pt and the field 44 pt), but the font-scale residual above remains.

Fix these issues in Kilo Cloud

Previous review (commit 711243f)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The new composer-shaped loading slot still stands ~6–7.5 dp shorter than ChatComposer's input row, so the transcript keeps a residual jump on the very path this change targets.

Assumptions: [INFERENCE] The dp values come from the repo's documented 14 pt NativeWind rem (h-11 = 38.5 dp, h-12 = 42 dp) and the composer's own constants (CONTROL_HIT_TARGET 48/44, field measureHeight 44 dp + border); I did not run the mounted suite or measure the frame on a device in this read-only session. The real row can also carry the paperclip and optional insert-newline control, which the placeholder does not paint, so its horizontal composition differs as well.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/components/agents/session-detail-skeleton.tsx 99 h-11/h-12 render 38.5/42 dp at the 14 pt rem while the composer's field is 44 dp and its controls are 48/44 dp, leaving the loading slot ~6–7.5 dp shorter than the row it replaces
Files Reviewed (2 files)
  • apps/mobile/src/components/agents/session-detail-skeleton.tsx - 1 issue
  • apps/mobile/src/components/agents/session-detail-content.tsx - 0 issues (formatting-only change)

Fix these issues in Kilo Cloud

Previous review (commit 416d917)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commit chooses the reserved bottom slot by the session's resolved kind and adds the composer placeholder for writable sessions, while preserving the read-only footer placeholder; no correctness, security, or performance defect was found in the changed files.

Assumptions: [INFERENCE] I did not run the mobile tests, typecheck, lint, or the design gate in this read-only session. The new branch keys off isReadOnly rather than activeSessionType; the SDK only recomputes isReadOnly once the session activity leaves connecting (packages/cloud-agent-sdk/src/session-manager.ts:2143-2153), so before the kind is known a read-only session can paint the composer skeleton and later swap to the footer placeholder. Whether that swap is visible in practice was not measured here.

Files Reviewed (8 files)
  • apps/mobile/src/components/agents/session-detail-content.tsx
  • apps/mobile/src/components/agents/session-detail-content.test.ts
  • apps/mobile/src/components/agents/session-detail-skeleton.tsx
  • apps/mobile/src/components/agents/session-detail-skeleton.test.ts
  • apps/mobile/src/components/agents/context-usage-display.ts
  • apps/mobile/src/components/agents/session-context-metrics.tsx
  • apps/mobile/src/components/agents/session-context-metrics-bidi.test.ts
  • apps/mobile/src/components/agents/session-terminal-error.ts (targeted re-verification of the previously fixed retryWith branches)

Previous review (commit 2581362)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commit replaces the fixed-width w-11 placeholder with a full-row overlay over the reserved dash row, so the loading bar now inherits the settled text metrics; no correctness, security, or performance defect was found in the four changed files.

Assumptions: [INFERENCE] I did not run the mobile test suite, typecheck, lint, or the design gate in this read-only session. On Android the placeholder row is hidden with importantForAccessibility="no" on the wrapper, which does not itself hide descendants; whether TalkBack still exposes the invisible primary — text was not verified on a device.

Files Reviewed (4 files)
  • apps/mobile/src/components/agents/context-usage-display.ts
  • apps/mobile/src/components/agents/context-usage-display.test.ts
  • apps/mobile/src/components/agents/session-context-metrics.tsx
  • apps/mobile/src/components/agents/session-context-metrics.test.ts

Previous review (commit 81d14cf)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The incremental commit paints the loading value slot as one bar; its only new concern is that the fixed w-11 bar matches the settled dash row only at the default font scale.

Assumptions: [INFERENCE] I did not run the mobile test suite or the design gate in this read-only session; the finding rests on the app's 14 px rem spacing scale and on the shared Text primitive scaling with the live system font scale.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
apps/mobile/src/components/agents/session-context-metrics.tsx 107 The fixed w-11 loading bar equals the settled — ${dash} row only at font scale 1, so the pill can still change width at settle on non-default font scales.
Files Reviewed (4 files)
  • apps/mobile/src/components/agents/context-usage-display.ts - 0 issues
  • apps/mobile/src/components/agents/context-usage-display.test.ts - 0 issues
  • apps/mobile/src/components/agents/session-context-metrics.tsx - 1 issue
  • apps/mobile/src/components/agents/session-context-metrics.test.ts - 0 issues

Previous review (commit 630f55d)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The only change since the last review removes the export modifier from the module-local PillCostSlot type; it has no other references in apps/mobile, so nothing breaks.

Assumptions: [INFERENCE] The full pnpm typecheck and check:unused were not run in this read-only session; the safety conclusion rests on a repository-wide grep showing PillCostSlot is referenced only inside context-usage-display.ts.

Files Reviewed (1 file)
  • apps/mobile/src/components/agents/context-usage-display.ts

Previous review (commit 924a3c1)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (4 files)
  • apps/mobile/src/components/agents/context-usage-display.ts
  • apps/mobile/src/components/agents/context-usage-display.test.ts
  • apps/mobile/src/components/agents/session-context-metrics.tsx
  • apps/mobile/src/components/agents/session-context-metrics.test.ts

Previous review (commit cd4741e)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The incremental commit cd4741e58e makes the reserved loading slots visible (bg-muted-soft bars, a w-8 cost bar), but the cost bar is wider than the dash it becomes once the open settles without usage, so the header pill now shrinks ~19 dp on the empty/failure path where it previously held its width.

Assumptions: [INFERENCE] (1) the fixed-width cost bar has no shrink/min-w-0, unlike the cost Text it replaces, so on a header narrower than ~104 dp the pill could overflow; not reproduced, and the gate's compact cell reports 0 errors. (2) The route-level loading screen still reserves the composer-shaped SessionComposerSkeleton (apps/mobile/src/app/(app)/agent-chat/[session-id].tsx:295) while SessionDetailContent reserves the taller read-only footer placeholder, so the bottom slot changes height at that handoff; unchanged code, not measured in this run. (3) With an empty open the pill stays visible and pressable while its accessibilityLabel is still empty, so the loading pill is announced as an unlabeled button (the new bar is hidden from assistive tech); that logic is unchanged by this commit. Suite results and device/design-gate runs were not re-executed here; findings are from code and the PR's own measurements.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/components/agents/session-context-metrics.tsx 110 w-8 reserves ~28 dp while loading, but costPending only holds during loading; once the open settles with no usage (empty read-only session, or the load-error frame) the slot becomes the narrow — dash, shrinking the pill ~19 dp at settle on a path that previously did not move.
Files Reviewed (6 files)
  • apps/mobile/src/components/agents/context-usage-display.ts - 0 issues
  • apps/mobile/src/components/agents/context-usage-display.test.ts - 0 issues
  • apps/mobile/src/components/agents/session-context-metrics.tsx - 1 issue
  • apps/mobile/src/components/agents/session-context-metrics.test.ts - 0 issues
  • apps/mobile/src/components/agents/session-detail-skeleton.tsx - 0 issues
  • apps/mobile/src/components/agents/session-detail-skeleton.test.ts - 0 issues

The earlier retryWith finding on session-terminal-error.ts is resolved at this head: session-owned agent-connection-lost indicators reopen the session, and only the exhausted app-wide leg uses connection.

Previous review (commit 14f4a7f)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commit 14f4a7f59 reserves the bottom slot with a read-only-shaped placeholder while an empty open resolves, and gives the context pill two dash-marked value slots before usage arrives; both match their updated tests, and no new correctness or security issue was found on the changed lines.

Assumptions: [INFERENCE] two layout concerns are unproven and were not reproduced: (1) the route-level loading screen still reserves the composer-shaped SessionComposerSkeleton (apps/mobile/src/app/(app)/agent-chat/[session-id].tsx:295) while SessionDetailContent now reserves the taller read-only footer geometry, so the bottom slot changes height at that handoff; (2) during a pending open the pill renders two visible em dashes while its accessibilityLabel is empty (no context info, no cost), which a screen reader may read from the children as "dash dash". The PR's design-gate run reports 0 errors at this head.

Files Reviewed (6 files)
  • apps/mobile/src/components/agents/context-usage-display.ts
  • apps/mobile/src/components/agents/context-usage-display.test.ts
  • apps/mobile/src/components/agents/session-context-metrics.test.ts
  • apps/mobile/src/components/agents/session-detail-content.tsx
  • apps/mobile/src/components/agents/session-detail-content.test.ts
  • apps/mobile/src/components/agents/session-detail-skeleton.tsx

Scope note: the incremental commit also adds the pill's dash slots and SessionReadOnlyFooterPlaceholder, neither of which the PR description's Change section covers; the description was not re-checked against them.

Previous review (commit bd993bf)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commit bd993bfc81 splits the lost-transport Retry by cause — agent-connection-lost indicators now reopen the session while only the exhausted app-wide leg resets the shared socket — resolving the prior WARNING; both branches are asserted in the mounted suite and the resolver/target lines match current code.

Assumptions: [INFERENCE] runtime reconnect behaviour of switchSession versus connection.retryConnection() was verified from the SDK sources and the PR's mounted tests, not from a device run of the session-owned loss path.

Files Reviewed (3 files)
  • apps/mobile/src/components/agents/session-terminal-error.ts
  • apps/mobile/src/components/agents/session-terminal-error.test.ts
  • apps/mobile/src/components/agents/session-detail-content.test.ts

Resolved since the previous review: the shared retryWith: 'connection' on the session's own agent-connection-lost indicator (now 'session'), and the app-wide leg correctly keeps 'connection' so the spent reconnect budget is actually cleared.

Previous review (commit 1cd5ee4)

Status: 1 Issue Found | Recommendation: Address before merge

Executive Summary

The skeleton and app-wide Retry fixes land correctly, but the new retryWith: 'connection' applies to the session's own agent-connection-lost indicator too, so a lost cloud-agent transport now retries the unrelated shared user-web socket instead of reopening the session.

Assumptions: [INFERENCE] the cloud-agent reachability of agent-connection-lost (empty open, transport disconnect) was verified from SDK code (cloud-agent-transport.ts, status-indicator.ts, session-manager.ts), not from a device run.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/components/agents/session-terminal-error.ts 208 connectionLostFailure sets retryWith: 'connection' for both the app-wide leg and the session's own agent-connection-lost indicator; a lost cloud-agent transport needs switchSession, not the shared user-web retry.
Files Reviewed (5 files)
  • apps/mobile/src/components/agents/session-terminal-error.ts - 1 issue
  • apps/mobile/src/components/agents/session-detail-content.tsx - 0 issues
  • apps/mobile/src/components/agents/session-detail-skeleton.tsx - 0 issues (previous flex-item finding fixed: bubbles are now the list's flex items with shrink, container has overflow-hidden)
  • apps/mobile/src/components/agents/session-terminal-error.test.ts - 0 issues
  • apps/mobile/src/components/agents/session-detail-content.test.ts - 0 issues

Fix these issues in Kilo Cloud

Previous review (commit 06e4594)

Status: 2 Issues Found | Recommendation: Address before merge

Executive Summary

The session-state consolidation is sound, but the skeleton shrink change cannot prevent the described clipping, and the new app-wide "connection lost" Retry re-opens over the same exhausted socket instead of restoring it.

Assumptions: [INFERENCE] the rounded dialog/error card behavior on the app-wide leg was verified only from the SDK reconnect code (user-web-connection.ts, base-connection.ts), not from a device run; the skeleton finding is based on React Native/Yoga flex semantics (the bubble is not the overflowing flex item) and was not measured on a device in this review.

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/components/agents/session-detail-skeleton.tsx 46 shrink sits on the Skeleton, but the overflowing flex item is the auto-height row wrapper, so the bubble cannot compress and the clipping this PR targets can still occur (also lines 49, 52).
apps/mobile/src/components/agents/session-terminal-error.ts 412 The app-wide exhausted leg becomes a terminal error whose only action is manager.switchSession; only connection.retryConnection()/retryReconnect() resets the reconnect budget, so this new Retry cannot recover the socket.
Files Reviewed (5 files)
  • apps/mobile/src/components/agents/session-terminal-error.ts - 1 issue
  • apps/mobile/src/components/agents/session-detail-content.tsx - 0 issues
  • apps/mobile/src/components/agents/session-detail-skeleton.tsx - 1 issue
  • apps/mobile/src/components/agents/session-terminal-error.test.ts - 0 issues
  • apps/mobile/src/components/agents/session-detail-content.test.ts - 0 issues

Fix these issues in Kilo Cloud


Reviewed by deepseek-flash-latest · Input: 88.4K · Output: 7.2K · Cached: 489.5K

Review guidance: REVIEW.md from base branch main

@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. labels Nov 1, 2026
Two review findings on the lost-connection state.

The skeleton rows were the flex items that overflowed, and each bubble sat in
an auto-height wrapper, so flexShrink on the bubble could not reduce anything.
The bubbles are now the list's own flex items, each with its margin and
alignment, and the container clips, so a body shorter than the placeholder
compresses the shapes instead of letting one be sliced.

A spent reconnect budget is only cleared by resetting the shared transport
leg, so the terminal error now carries which recovery its Retry runs:
reopening the session for a failure the session reported, and the connection
retry for a lost leg.
@iscekic iscekic removed merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. labels Nov 1, 2026
@iscekic

iscekic commented Nov 1, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Re-ran the design gate at the new head and added the placeholder capture.

  • Design gate: 4 cells, 0 errors, 4 warnings (the pre-existing voice-input touch target on every cell; the merge-base run reports the same four).
  • Placeholder: with the app's metadata read stalled, the three placeholder bubbles keep their 3/4, 1/2 and 2/3 widths, sit above the composer, and end 11 dp above the body's bottom edge — nothing is sliced.

Design gate contact sheet at the new head

Placeholder while the session is still loading

@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. labels Nov 1, 2026
The recovery a lost transport's Retry runs depends on which transport it was:
a session whose own transport dropped reopens, while the shared app-wide leg
needs its spent reconnect budget reset. Pass the action per branch instead of
returning the connection retry for both.
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. and removed merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head labels Nov 1, 2026
@iscekic iscekic added the merge:blocked:design-review Design review has not cleared this change label Nov 1, 2026
While a session transcript was still loading, the screen painted an
editable Message composer at the bottom. The open's transport has not
answered in that window, so a read-only session replaced that composer
with the read-only footer as soon as it resolved: one screen showing
writing controls it did not have, then a jump to the state it did have.

The bottom slot is now the session's own. Until the transcript resolves —
and while no failure, agent status or pending ask means the session has
already spoken — the slot holds the read-only footer's area, at the
footer's own geometry, so the footer lands where the placeholder was.
The composer keeps its place behind blocking cards and failures.

The header context pill showed a bare ring in that same window, then grew
its value when usage arrived. A session that has not reported usage now
keeps both value slots, marked with the dash, so the pill fills the slots
it already showed instead of changing shape late.
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:blocked:insufficient-e2e Verification does not cover the change merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. and removed merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. labels Nov 1, 2026
@iscekic

iscekic commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Design gate and device frames re-run at head b5b761e5b after merging main (the merge kept main's pill cost formatter and pinned cost run inside this branch's cost-slot model).

  • Design gate, matrix standard, agent-chat route of the dev fixture session, emulator at the default text size and density: 4 cells, 0 errors, 0 warnings.
  • Merge base e38da3eab, same link and same staging: 4 cells, 0 errors, 4 warnings — the composer voice control's pre-existing touch target in every cell.
  • Device pair with one staging: the session's transcript socket is stalled and its metadata has arrived.
    • merge base: the failure card with Retry, a second The session could not be loaded. Retry first. line under it, and the composer.
    • this head: the same failure card with Retry and no second line — one failure, one statement.
  • Loading window, same device: the base paints an editable composer; this head paints the reserved footer area (a note line and a 48 dp button bar) instead, and the header pill's value row is one bar with no dash beside it.

Screen text and element bounds below are read from a uiautomator dump taken in the same command as each capture.

Design gate cells at this head and at the merge base

Loading window: reserved footer area, no composer

Failed open: one failure statement with Retry

Merge base, same staging: the failure plus a second statement of it

@iscekic iscekic added merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. merge:blocked:human-design The owner reviews the design labels Nov 2, 2026
@iscekic

iscekic commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

bot: The automatic design review hands this pull request to the owner at head b5b761e.
These findings still block it after the automatic fix runs:

  • base / app-agent-chat-ses-00000000000liveclistream01, the composer skeleton at the bottom (short centered bar above a wide bar): The loading composer is a two-row placeholder that does not match the final single-row composer with its Message field, mic and send buttons, so the composer and the message list will change shape when loading ends. Fix: Shape the composer skeleton like the real composer: one pill-height row with a field-width block and two circular button blocks at the real composer's height, and drop the extra centered bar.
  • base / app-agent-chat-ses-00000000000liveclistream01, the wide composer skeleton bar: The bar uses a dark taupe fill far heavier than the message skeletons, so a placeholder becomes the most prominent element on the loading screen. Fix: Use the same muted skeleton fill token as the message bubble skeletons, with the matching token in dark mode instead of the bright gray.
    The owner proxy could not decide it: Both findings are on the merge base capture: the two-row composer skeleton and its heavy fill already exist before this PR. This change only affects the transcript skeleton and the lost-connection state. They are pre-existing gaps, not regressions from this PR, so they should go to their own work item rather than block this fix.
    Send it back with these fixes, accept the design, or close it.

The pending open reserved the read-only footer's area for every session, so a
writable session held a 97.9 dp footer block and then swapped to its composer
when the open resolved. Choose the reserve from the session's own kind: a
read-only session keeps the footer's area (which lands on it unchanged), and
every other session keeps the composer's, using the composer skeleton the route
already paints before the metadata arrives. Both are placeholders, so neither
offers a control, and the composer now lands on the geometry its skeleton holds.
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. and removed merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head labels Nov 2, 2026
The slot that stands in for the composer now mirrors the composer's input row:
the field's 44 dp box and the two trailing controls at their tap-target size in
the row's own gap, instead of one bar. Come with the placeholder's fill, so the
loading frame reads as the composer landing rather than as a different row.
@iscekic iscekic removed the merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. label Nov 2, 2026
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. labels Nov 2, 2026
@iscekic

iscekic commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Pending-open frames for both halves of the bottom slot, captured on the emulator at the default text size and density against the local stack, with the transcript read held so the loading window stays open.

  • Writable open (a session whose kind resolves with a writer), before this head: the slot paints the read-only footer's reserve — a 17.5 dp note bar and a 48 dp action bar in the footer's own gray, inside the secondary band.
  • Same open, at this head: the slot paints the composer's own row — the field's box and the two trailing controls, in the placeholder fill, on the page background, so the composer lands where the reserve was.
  • Read-only link used by the design gate: its reserve is the footer's shape and the footer lands on it at the same frame, unchanged by this head.
  • Design gate at this head, agent chat session route, standard matrix: 4 cells, 0 errors, 4 warnings — one unsettled warning per cell because the read was held open for this capture, not a layout defect.

Attachments: the two pending frames, then the gate's contact sheet.

Pending open before: the read-only footer reserve

Pending open at this head: the composer row in the placeholder fill

Design gate contact sheet at this head

@iscekic

iscekic commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Answering the two findings of the design handoff at head b5b761e, on the pull request as a whole because they arrived outside a review thread.

  1. The loading composer's shape — valid, fixed, and the cause was wider than the composer. The reserve that stands in for the bottom slot consulted no session kind, so every pending open held the read-only footer's area: a 17.5 dp note bar and a 48 dp action bar, then the composer's own row when the open resolved. The slot now takes the shape its session lands in — a read-only session keeps the footer's area, every other session keeps the composer's — and the composer's placeholder land is the composer's own row now: the field's 44 dp box and the two trailing controls at their tap-target size in the row's gap, instead of a single bar and a centered line above it. Measured on the emulator with the transcript read held open: the writable open painted the footer's two bars at y 2112–2152 px and y 2184–2308 px before the change, and paints the composer row (field box plus two controls) after it, with no footer gray anywhere in the frame.
  2. The wide bar's fill — the suggested token cannot carry the reserve. The theme's muted equals secondary in both palettes (light #f0eee6/#f0eee6, dark #1f1f24/#1f1f24, and guard tests in three other components pin that), and the reserved area paints the footer's own bg-secondary; bars in the message bubbles' token would paint the footer's fill exactly, which is the blank tinted block an earlier review asked to be fixed. bg-muted-soft is this app's token for a bar on a secondary surface — the profile credits card, the hub card, the PR review checks and the header pill all use it — and it is lighter than the filled Continue button the bar reserves. Kept, and recorded in the pull request body. The finding's other half is answered by the shape change: the reserve now only paints that gray for sessions that really will fill the footer's action.

Design gate at the new head: 4 cells, 0 errors, 4 warnings (one unsettled per cell, because the read was held open for the capture).

The placeholder row used scale steps, and this app's rem is 14 pt: `h-11` paints
38.5 pt where the field is 44 pt and the two controls are 48 pt on Android and
44 pt on iOS, so the slot fell 6 pt short of the row it replaced and the
transcript moved when the composer landed. Write the sizes in points and pin
them, with the rem trap asserted so a scale step cannot come back.
@iscekic iscekic removed merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. labels Nov 2, 2026
@iscekic

iscekic commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Two changes since the last run, both from the review.

  • The composer's loading row now stands on the composer's own points: the field at 44 pt and the two controls at 48 pt on Android / 44 pt on iOS, written in points because this app's rem is 14 pt (a scale step painted 38.5 pt). Measured on the emulator afterwards: the controls stand 124 px tall, 48 pt at 2.625 density, on the page background with no footer gray in the frame.
  • The design gate and its merge-base run were both re-run at this head. Head: 4 cells, 0 errors, 4 warnings (one unsettled per cell, because the transcript read was held open for the capture). Merge base: 4 cells, 0 errors, 8 warnings — the same four unsettled ones plus the composer voice control's pre-existing touch target in every cell, which the head's captured frame does not paint.

Attachments: the pending frame at this head, then the gate's contact sheet.

Pending open at this head: the composer row in the placeholder fill

Design gate cells at this head and at the merge base

The slot pinned its field at 44 pt, but the composer's field is its input's line
box times the system font scale plus the input's vertical padding, so a larger
text size made the composer taller than the slot that stood in for it. The field
takes the same rule from the window's font scale; the two controls keep their
fixed tap-target size. The suite pins the height at scale 1 and at a larger
scale.
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. labels Nov 2, 2026
@iscekic

iscekic commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Final run for this head, after the font-scale fix.

  • Pending open of a writable session, transcript read held: the slot paints the composer's row — the field's box and the two trailing controls — in the placeholder fill on the page background. Measured in the attached frame: the controls stand at y 2188–2312 px, 124 px, 48 pt at 2.625 density; no footer gray appears anywhere in the frame.
  • Design gate at this head, agent chat session route, standard matrix: 4 cells, 0 errors, 4 warnings (one unsettled per cell, because the read was held open for the capture).
  • Merge base e38da3eab, same link and staging: 4 cells, 0 errors, 4 warnings — the same unsettled ones. Nothing this change paints is flagged in either run.

The field's box now follows the composer's own rule (its input line box times the system font scale plus the input's vertical padding), so it is 44 pt at the default text size and grows with a larger one instead of staying fixed.

Attachments: the pending frame at this head, then the gate's contact sheet.

Pending open at this head: the composer row in the placeholder fill

Design gate cells at this head and at the merge base

…poser slot

Two ways the slot still disagreed with the composer it stands in for: it read the
window's font scale, which goes stale when the text size changes without a
remount, where the composer reads the app's live one; and it left out the field's
1 pt border on each side. Take the live scale and the border, and pin both.
@iscekic

iscekic commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Re-ran both design-gate runs at this head after the live-font-scale and field-border fixes.

  • Head: 4 cells, 0 errors, 4 warnings (one unsettled per cell — the transcript read is held open for the capture, so the cells describe the loading frame rather than a settled one).
  • Merge base e38da3eab, same link and staging: 4 cells, 0 errors, 4 warnings, the same unsettled ones. Nothing this change paints is flagged in either run.
  • Pending open of a writable session, read held: the slot paints the composer's row — the field's box and the two trailing controls — in the placeholder fill on the page background. The controls stand at y 2188–2312 px, 124 px, 48 pt at 2.625 density; no footer gray appears anywhere in the frame.

Attachments: the pending frame at this head, then the gate's contact sheet.

Pending open at this head: the composer row in the placeholder fill

Design gate cells at this head and at the merge base

@iscekic

iscekic commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Answering the coverage note in the kilobot summary at this head, as a comment on the pull request because it arrived in the review body rather than in a thread.

The note is accurate as a coverage observation, and it is not a defect in the shipped behaviour:

  • SessionComposerSkeleton calls useLiveFontScale() from @/lib/font-scale/system-font-scale-context, the same hook ChatComposer reads, so the slot follows a text-size change without a remount.
  • session-detail-skeleton.test.ts mocks useWindowDimensions and useLiveFontScale from one shared value, so a change back to the window hook would still pass that suite. The suite does still pin the composer's height rule: 46 pt at scale 1 and 20 * 1.3 + 24 + 2 at a larger scale.

The reason it stays a note rather than a commit on this head: the evidence for this slot is a frame measured on the device at this exact commit, and the checks and the review history for it are anchored there too. Making the two mocked hooks disagree is worth doing as test hardening, and it belongs with the next change that touches this placeholder, so it lands on top of a code path that is already verified instead of moving that device frame, the design-gate run and the review for a test-only edit.

@iscekic

iscekic commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Re-ran the design gate at this head and its merge base with one staging, so the two runs can be attributed to each other.

  • Head 282ec6c01, agent chat session route, standard matrix: 4 cells, 0 errors, 4 warnings. Each warning is the same unsettled one — the fixture session streams while the cells are measured (11–13 s against the gate's 8 s settle window) — so no clipping, layout-shift, touch-target, safe-area or RTL finding appears at this head.
  • Merge base e38da3eab, same link, same fixture, same ordering: 4 cells, 0 errors, 8 warnings — the same four unsettled ones, plus the composer voice control's touch target in all four cells. The head's captured frame does not paint that control, so the head run reports only the shared warnings.
  • Provenance, read from the probe dumps of both runs: the head cells mount the composer's placeholder row and never the composer, while the base cells mount the composer. The same screen state is captured in both (the fixture streaming, in each cell of both runs).

Both runs and the merge-base folder are uploaded for this head. The attached sheet is this run's contact sheet.

Design gate cells at this head

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge Do not merge this PR (yet) drain Work item from the findings drain explorer:android Finding surfaced by the Android explorer merge:bot Bot-owned review; merge when ready once readiness passes and a review approves.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants